<?php
/**
 * XGPHP 轻量级PHP框架
 * @link http://xgphp.xg3.cn
 * @version 2.0.0
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @author 讯高科技 <xungaokeji@qq.com>
*/
namespace xg;
use \xg\facade\App;
use \xg\facade\Lang;
use \xg\facade\Request;
use \helper\Arr;
use \helper\Str;
use \helper\File;
use \helper\Checker;

class Route {
	protected static $rules=[];
	protected $enabled=false;
	protected $pathinfo='';
	protected $var='s';
	protected $rename=[]; // 强制重命名的应用列表
	protected $alias=[]; // 应用别名列表
	protected $config=[];
	protected $data=[];
	protected $default=[];
	
	public function __construct($conf = null) {
		if(is_null($conf)) {
			$conf=App::get('config')->get('route',[]);
		}
		$this->config=$conf;
		$this->enabled=Arr::get($conf,'enabled',true);
		$this->rename=Arr::get($conf,'rename',[]);
		$this->alias=Arr::get($conf,'alias',[]);
		$this->var=Arr::get($conf,'var','s');
		$query = Request::input('get.');
		if($defApp = $query['app'] or $defApp = Arr::get($conf,'entry.app')){
			$this->default['app'] = $defApp;
		}
		$this->default['module'] = $query['module']?$query['module']:Arr::get($conf,'entry.module','index');
		$this->default['action'] = $query['action']?$query['action']:Arr::get($conf,'entry.action','index');
		$this->pathinfo = $query[$this->var]?$query[$this->var]:$_SERVER['PHTH_INFO'];
	}
	
	public function data() {
		return $this->data;
	}
	
	public function app(){
		return $this->data['app'];
	}
	
	public function module(){
		return $this->data['module'];
	}
	
	public function action(){
		return $this->data['action'];
	}
	
	public function buildUrl($url,$params=[]) {
		if($url=='/') {
			return $url;
		}
		$_arr=Arr::split(trim($url,'/'),'/');
		if(count($_arr)==0){
			$_arr = [
				$this->data['app'],
				$this->data['module'],
				$this->data['action']
			];
		}elseif(count($_arr)==1){
			$_arr = [
				$this->data['app'],
				$this->data['module'],
				$_arr[0]
			];
		}elseif(count($_arr)==2){
			$_arr = [
				$this->data['app'],
				$_arr[0],
				$_arr[1]
			];
		}
		
		$parse = $this->parseUrl(Arr::join($_arr,'/'));
		$data = Arr::extend($this->data,$parse,['params'=>$params]);

		$url=$data['module'].'/'.$data['action'];

		if($app=$data['app']) {
			if(is_array($this->rename) and $this->rename[$app]){
				$app=$this->rename[$app];
			}
			$url=$app.'/'.$url;
		}
		if(strpos($url,'/') > 0) {
			$url='/'.$url;
		}
		$ext=$data['ext']?$data['ext']:$this->config['ext'];
		if($ext) {
			$url.='.'.$ext;
		}
		if($data['query']) {
			$url.='?'.$data['query'];
		}
		if($data['params'] and ($params = http_build_query($data['params']))) {
			$url.=(strpos($url,'?')===false?'?':'&').$params;
		}
		return $url;
	}
	
	public function parseUrl($url) {
		$data = [];
		if($url) {
			if(is_string($url)) {
				if(strpos($url,'?')!==false) {
					$_arr1 = Arr::split($url,'?');
					$url = $_arr1[0];
					$data['query'] = $_arr1[1];
				}
				$_arr = Arr::split(trim($url,'/'),'/');

				if($_arr and $app = array_shift($_arr))$data['app'] = $app;
				if($_arr and $module = array_shift($_arr))$data['module'] = $module;
				if($_arr and $action = array_shift($_arr))$data['action'] = $action;
			} elseif(is_array($url)) {
				$data = $url;
			}
		}
		return $data;
	}
	
	protected function match($type) {
		$rules=static::$rules[$type];
		foreach($rules as $rule){
			if(preg_match('/'.$rule['rule'].'/i',$this->pathinfo,$matched)){
				$parsed = $this->parseUrl($rule['url']);
				$result = Arr::extend($rule,$parsed);
				if($rule['param']) {
					$_GET = Arr::extend($_GET,$rule['param']);
					$_REQUEST = Arr::extend($_REQUEST,$rule['param']);
				}
				foreach($rule['tags'] as $key=>$tag) {
					if(is_array($tag) and Checker::isCallable($tag[1])){
						$_GET[$tag[0]]=$tag[1]();
						$_REQUEST[$tag[0]]=$tag[1]();
					}else{
						$_GET[$tag]=$matched[$key+1];
						$_REQUEST[$tag]=$matched[$key+1];
					}
				}
				return $result;
			}
		}
	}
	
	protected function appName($app) {
		if(is_array($this->rename)) {
			if($this->rename[$app]) {
				throw new Exception(Lang::get('app "{:name}" not exist',['name'=>$app]),404);
			}
			$_rename = array_flip($this->rename);
			if($_rename[$app]){
				$app = $_rename[$app];
			}
		}
		if(is_array($this->alias)) {
			if($this->alias[$app]) {
				$app = $this->alias[$app];
			}
		}
		return $app;
	}
	
	public function dispatch() {
		// 如果开启了路由
		if($this->enabled) {
			unset($_GET[$this->var]);
			if(!$_POST[$this->var]) {
				unset($_REQUEST[$this->var]);
			}
			$method = Request::method();
			if($method=='POST') {
				$matched=$this->match('post');
			}
			if(!$matched) {
				$matched=$this->match('request');
			}
			if(!$matched) {
				$matched=$this->match('get');
			}
		}
		$data = $this->default;
		if($matched) // 如果匹配到了路由规则
		{
			$data = Arr::extend($data,$matched);
		}
		else // 没有匹配到路由规则 解析当前的URL
		{
			if ($this->pathinfo) {
				$parse = $this->parseUrl($this->pathinfo);
				$data = Arr::extend($data,$parse);
				if($data['app']) {
					$data['app']=$this->appName($data['app']);
				}
				$data = $this->stringFilter($data);
			}
		}
		// 回调函数
		if($data['handler'] and Checker::isCallable($data['handler'])){
			// $handler = $data['handler'];
			// $result = call_user_func($handler);
			// if ($result) {
			// 	$response=Response::content($result);
			// }
		} else {
			// // 是否缓存内容
			// if($data['cache']){
			// 	$response->cacheContent(true);
			// }
			$app=$data['app'];
			$module=Str::studly($data['module']);
			$action=$data['action'];
		
			if(is_dir(File::path('xg:apps'))) {
				if(!$app){
					$app = 'home';
				}
				$classname='\\apps\\'.$app.'\\module\\'.$module;
				if(!class_exists($classname)) {
					throw new Exception(Lang::get('module "{:module}" not found in app "{:app}"',['module'=>$module,'app'=>$app]),404);
				}
				File::alias('app',File::path('xg:apps',$app));
			} elseif(is_dir(File::path('xg:app'))) {
				$classname='\\app\\module\\'.$module;
				if(!class_exists($classname)){
					throw new Exception(Lang::get('module "{:module}" not found',['module'=>$module]),404);
				}
			} else {
				throw new Exception('app dir not exist',404);
			}
			if(!method_exists($classname,$action)){
				$message = Lang::get('action "{:action}" not found in module "{:class}"',['class'=>$classname,'action'=>$action]);
				throw new Exception($message,404);
			}
			$this->data = $data;
			$moduleIns = new $classname();
			$response = call_user_func([$moduleIns, $action]);
		}
		return $response;
	}
	public static function rule($rule,$url=null,$param=[],$ext=null) {
		return static::request($rule,$url,$param,$ext);
	}
	/**
	 * @param $type	规则类型
	 * @param $data	规则数据 数据可能键名包括 rule,url,app,module,action,ext,moved,handler,cache,param
	 * @return array
	*/
	public static function addRule(string $type,$data) {
		if($data['rule']) {
			$data['rule']=str_replace('/','\/',$data['rule']);
			// 替换< >符号包裹的参数
			if(preg_match('/\<([a-z0-9_]+)\>/i',$data['rule'])) {
				$data['rule']=preg_replace_callback('/\<([a-z0-9_]+)\>/i',function($matched)use(&$data){
					$matched=$matched[1];
					$data['param']=$data['param']?$data['param']:[];
					$data['tags']=$data['tags']?$data['tags']:[];
					$value=$data['param'][$matched];
					if($value and is_string($value)){
						unset($data['param'][$matched]);
						$data['tags'][]=$matched;
						return '('.trim($value,' ()').')';
					}elseif(Checker::isClosure($value)){
						$data['handler']=$value;
						unset($data['param'][$matched]);
						return '(.*+)';
					}else{
						return '(.*+)';
					}
				},$data['rule']);
			}
			if($data['url'] and $type!='redirect'){
				$arr=Arr::split(trim($data['url'],'/'),'/');
				if(!$data['app'] and !$data['module'] and !$data['action']){
					if(!$data['action'] and count($arr)>=1){
						$data['action']=array_pop($arr);
					}
					if(!$data['module'] and count($arr)>=1){
						$data['module']=array_pop($arr);
					}
					if(!$data['app'] and count($arr)>=1){
						$data['app']=array_pop($arr);
					}
				}
			}
			$ruleName=$data['name']?$data['name']:$data['rule'];
			static::$rules[$type][$ruleName]=$data;
		}
	}
	public static function get($rule,$url=null,$param=[],$ext=null) {
		if(is_array($rule)){
			$data=$rule;
		}else{
			$data=['rule'=>$rule];
			if($url){
				$data['url']=$url;
			}
			if($param){
				$data['param']=$param;
			}
			if($ext){
				$data['ext']=$ext;
			}
		}
		return static::addRule('get',$data);
	}
	public static function post($rule,$url=null,$param=[]) {
		if(is_array($rule)){
			$data=$rule;
		}else{
			$data=['rule'=>$rule];
			if($url){
				if(is_string($url)){
					$data['url']=$url;
				}elseif(Checker::isCallable($url)){
					$data['handler']=$url;
				}
			}
			if($param){
				$data['param']=$param;
			}
		}
		return static::addRule('post',$data);
	}
	public static function request($rule,$url=null,$param=[],$ext=null) {
		if(is_array($rule)){
			$data=$rule;
		}else{
			$data=['rule'=>$rule];
			if($url){
				if(is_string($url)){
					$data['url']=$url;
				}elseif(Checker::isClosure($url)){
					$data['handler']=$url;
				}
			}
			if($param){
				$data['param']=Arr::extend($param,$data['param']);
			}
			if($ext){
				$data['ext']=$ext;
			}
		}
		return static::addRule('request',$data);
	}
	public static function redirect($rule,$url=null,$moved=false) {
		if(is_array($rule)){
			$data=$rule;
		}else{
			$data=['rule'=>$rule];
			$data['url']=$url;
			$data['moved']=$moved;
		}
		return static::addRule('redirect',$data);
	}
	protected function stringFilter($data) {
		if(is_array($data)){
			$newData=[];
			foreach($data as $k=>$rule){
				$k=$this->stringFilter($k);
				$rule=$this->stringFilter($rule);
				$newData[$k]=$rule;
			}
			return $newData;
		}elseif(is_string($data)){
			return preg_replace('/[^a-zA-Z0-9_]/','',$data);
		}else{
			return $data;
		}
	}
}
?>